﻿using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.Practices.EnterpriseLibrary.Logging;
using Microsoft.Practices.EnterpriseLibrary.Logging.Filters;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.InterceptionExtension;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using vm.Aspects.Facilities;

namespace vm.Aspects.Policies.Tests
{
    [TestClass]
    public class CallTraceCallHandlerTest
    {
        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        public TestContext TestContext { get; set; }

        LogWriter LogWriter { get; set; }

        #region Additional test attributes
        //
        // You can use the following additional attributes as you write your tests:
        //
        // Use ClassInitialize to run code before running the first test in the class
        //[ClassInitialize()]
        //public static void MyClassInitialize(TestContext testContext) 
        //{
        //}
        //
        // Use ClassCleanup to run code after all tests in a class have run
        // [ClassCleanup()]
        // public static void MyClassCleanup() { }
        //
        // Use TestInitialize to run code before running each test 
        [TestInitialize()]
        public void MyTestInitialize()
        {
            if (LogWriter == null)
                LogWriter = DIContainer.Root.Resolve<Lazy<LogWriter>>().Value;

            TestTraceListener.Reset();
        }
        //
        // Use TestCleanup to run code after each test has run
        [TestCleanup()]
        public void MyTestCleanup()
        {
            foreach (var msg in TestTraceListener.Messages)
                TestContext.WriteLine(msg);
        }
        //
        #endregion

        interface ITested
        {
            string Method1(string input);
            string Method2(string input);
            string Method3(string input1, string input2);
            void Method4(string input1, string input2);
        }

        class Tested : ITested
        {
            #region ITested Members

            public string Method1(string input)
            {
                //Thread.Sleep(100);
                return input;
            }

            public string Method2(string input)
            {
                //Thread.Sleep(100);
                throw new NotImplementedException();
            }

            public string Method3(string input1, string input2)
            {
                //Thread.Sleep(100);
                return input1+"_"+input2;
            }

            public void Method4(string input1, string input2)
            {
                //return input1+"_"+input2;
            }

            #endregion
        }

        [ExpectedException(typeof(ArgumentNullException))]
        [TestMethod]
        public void ConstructorNullLogWriterTest()
        {
            try
            {
                LogWriter logger = null;

                var target = new CallTraceCallHandler(logger);
            }
            catch (Exception x)
            {
                TestContext.WriteLine("{0}", x.DumpString());
                throw;
            }
        }

        [TestMethod]
        public void LogPriorityFilteredShouldNotLogTest()
        {
            try
            {
                LogWriter.Configure(
                    cfg =>
                    {
                        cfg.Filters.Add(new PriorityFilter("TestPriorityFilter", 1));
                    });

                var target = new CallTraceCallHandler(LogWriter)
                {
                    LogBeforeCall = true,
                    Priority      = 0,
                };
                ITested tested = new Tested();
                var call = new MethodInvocationMock(
                                    tested,
                                    typeof(ITested).GetMethod("Method1"),
                                    new ParameterCollection(
                                            new object[] { "1" },
                                            typeof(ITested).GetMethod("Method1").GetParameters(),
                                            p => true));

                var ret = target.Invoke(call, () => call.InvokeNext);

                Assert.AreEqual("1", ret.ReturnValue);
                Assert.AreEqual(1, TestTraceListener.Messages.Count());
            }
            catch (UnitTestAssertException)
            {
                throw;
            }
            catch (Exception x)
            {
                TestContext.WriteLine("{0}", x.DumpString());
                throw;
            }
            finally
            {
                LogWriter.Configure(
                    cfg =>
                    {
                        cfg.Filters.Remove(
                            cfg.Filters.Where(f => f.Name == "TestPriorityFilter").First());
                    });

            }
        }

        [TestMethod]
        public void LogDisabledFilterShouldNotLogTest()
        {
            try
            {
                LogWriter.Configure(
                    cfg =>
                    {
                        cfg.Filters.Add(new LogEnabledFilter("TestLogEnabledFilter", false));
                    });

                var target = new CallTraceCallHandler(LogWriter)
                {
                    LogBeforeCall = true,
                    Priority      = 0,
                };
                ITested tested = new Tested();
                var call = new MethodInvocationMock(
                                    tested,
                                    typeof(ITested).GetMethod("Method1"),
                                    new ParameterCollection(
                                            new object[] { "1" },
                                            typeof(ITested).GetMethod("Method1").GetParameters(),
                                            p => true));

                var ret = target.Invoke(call, () => call.InvokeNext);

                Assert.AreEqual("1", ret.ReturnValue);
                Assert.AreEqual(1, TestTraceListener.Messages.Count());
            }
            catch (UnitTestAssertException)
            {
                throw;
            }
            catch (Exception x)
            {
                TestContext.WriteLine("{0}", x.DumpString());
                throw;
            }
            finally
            {
                LogWriter.Configure(
                    cfg =>
                    {
                        cfg.Filters.Remove(
                            cfg.Filters.Where(f => f.Name == "TestLogEnabledFilter").First());
                    });

            }
        }


        [TestMethod]
        public void LogDisabledShouldNotLogTest()
        {
            try
            {
                LogWriter.Configure(
                    cfg =>
                    {
                        cfg.IsLoggingEnabled = false;
                    });

                var target = new CallTraceCallHandler(LogWriter)
                {
                    LogBeforeCall = true,
                    Priority      = 0,
                };
                ITested tested = new Tested();
                var call = new MethodInvocationMock(
                                    tested,
                                    typeof(ITested).GetMethod("Method1"),
                                    new ParameterCollection(
                                            new object[] { "1" },
                                            typeof(ITested).GetMethod("Method1").GetParameters(),
                                            p => true));

                var ret = target.Invoke(call, () => call.InvokeNext);

                Assert.AreEqual("1", ret.ReturnValue);
                Assert.AreEqual(1, TestTraceListener.Messages.Count());
            }
            catch (UnitTestAssertException)
            {
                throw;
            }
            catch (Exception x)
            {
                TestContext.WriteLine("{0}", x.DumpString());
                throw;
            }
            finally
            {
                LogWriter.Configure(
                    cfg =>
                    {
                        cfg.IsLoggingEnabled = true;
                    });

            }
        }


        [TestMethod]
        public void LogDisabledFilterShouldNotLogAfterTest()
        {
            try
            {
                LogWriter.Configure(
                    cfg =>
                    {
                        cfg.Filters.Add(new LogEnabledFilter("TestLogEnabledFilter", false));
                    });

                var target = new CallTraceCallHandler(LogWriter)
                {
                    LogBeforeCall = true,
                    Priority      = 0,
                };
                ITested tested = new Tested();
                var call = new MethodInvocationMock(
                                    tested,
                                    typeof(ITested).GetMethod("Method1"),
                                    new ParameterCollection(
                                            new object[] { "1" },
                                            typeof(ITested).GetMethod("Method1").GetParameters(),
                                            p => true));

                var ret = target.Invoke(call, () => call.InvokeNext);

                Assert.AreEqual("1", ret.ReturnValue);
                Assert.AreEqual(1, TestTraceListener.Messages.Count());
            }
            catch (UnitTestAssertException)
            {
                throw;
            }
            catch (Exception x)
            {
                TestContext.WriteLine("{0}", x.DumpString());
                throw;
            }
            finally
            {
                LogWriter.Configure(
                    cfg =>
                    {
                        cfg.Filters.Remove(
                            cfg.Filters.Where(f => f.Name == "TestLogEnabledFilter").First());
                    });

            }
        }


        [TestMethod]
        public void LogDisabledShouldNotPostAfterTest()
        {
            try
            {
                LogWriter.Configure(
                    cfg =>
                    {
                        cfg.IsLoggingEnabled = false;
                    });

                var target = new CallTraceCallHandler(LogWriter)
                {
                    Priority      = 0,
                };
                ITested tested = new Tested();
                var call = new MethodInvocationMock(
                                    tested,
                                    typeof(ITested).GetMethod("Method1"),
                                    new ParameterCollection(
                                            new object[] { "1" },
                                            typeof(ITested).GetMethod("Method1").GetParameters(),
                                            p => true));

                var ret = target.Invoke(call, () => call.InvokeNext);

                Assert.AreEqual("1", ret.ReturnValue);
                Assert.AreEqual(1, TestTraceListener.Messages.Count());
            }
            catch (UnitTestAssertException)
            {
                throw;
            }
            catch (Exception x)
            {
                TestContext.WriteLine("{0}", x.DumpString());
                throw;
            }
            finally
            {
                TestTraceListener.Messages.Select(
                    m => { TestContext.WriteLine(m); return 1; }).Count();

                LogWriter.Configure(
                    cfg =>
                    {
                        cfg.IsLoggingEnabled = true;
                    });

            }
        }

        [ExpectedException(typeof(ArgumentNullException))]
        [TestMethod]
        public void InvokeGetNextNullTest()
        {
            try
            {
                var target = new CallTraceCallHandler(LogWriter);
                ITested tested = new Tested();
                var call = new MethodInvocationMock(
                                    tested,
                                    typeof(ITested).GetMethod("Method1"),
                                    new ParameterCollection(
                                            new object[] { "1" },
                                            typeof(ITested).GetMethod("Method1").GetParameters(),
                                            p => true));

                var ret = target.Invoke(call, null);
            }
            catch (UnitTestAssertException)
            {
                throw;
            }
            catch (Exception x)
            {
                TestContext.WriteLine("{0}", x.DumpString());
                throw;
            }
        }

        [TestMethod]
        public void Method1DefaultTraceCallTest()
        {
            try
            {
                var target = new CallTraceCallHandler(LogWriter);

                target.LogAsynchronously = false;

                ITested tested = new Tested();
                var call = new MethodInvocationMock(
                                    tested,
                                    typeof(ITested).GetMethod("Method1"),
                                    new ParameterCollection(
                                            new object[] { "1" },
                                            typeof(ITested).GetMethod("Method1").GetParameters(),
                                            p => true));

                var sw = new Stopwatch();

                sw.Start();
                var ret = target.Invoke(call, () => call.InvokeNext);
                sw.Stop();

                Thread.Sleep(300);

                TestContext.WriteLine(
                    @"Total call duration: {0:d\.hh\:mm\:ss\.fffffff}",
                    sw.Elapsed);

                Assert.AreEqual("1", ret.ReturnValue);
                Assert.AreEqual(2, TestTraceListener.Messages.Count());
            }
            catch (UnitTestAssertException)
            {
                throw;
            }
            catch (Exception x)
            {
                TestContext.WriteLine("{0}", x.DumpString());
                throw;
            }
        }

        [TestMethod]
        public void Method3DefaultTraceAsyncCallTest()
        {
            try
            {
                var target = new CallTraceCallHandler(LogWriter)
                {
                    LogAsynchronously = true,
                    IncludePrincipal  = false,
                    LogBeforeCall     = true,
                };

                ITested tested = new Tested();
                var call = new MethodInvocationMock(
                                    tested,
                                    typeof(ITested).GetMethod("Method3"),
                                    new ParameterCollection(
                                            new object[] { "1", "2" },
                                            typeof(ITested).GetMethod("Method3").GetParameters(),
                                            p => true));

                var sw = new Stopwatch();

                sw.Start();
                var ret = target.Invoke(call, () => call.InvokeNext);
                sw.Stop();

                TestContext.WriteLine(
                    @"Total call duration: {0:d\.hh\:mm\:ss\.fffffff}",
                    sw.Elapsed);

                Thread.Sleep(400);

                Assert.AreEqual("1_2", ret.ReturnValue);
                Assert.AreEqual(3, TestTraceListener.Messages.Count());
            }
            catch (UnitTestAssertException)
            {
                throw;
            }
            catch (Exception x)
            {
                TestContext.WriteLine("{0}", x.DumpString());
                throw;
            }
        }

        [TestMethod]
        public void Method4DefaultTraceAsyncCallTest()
        {
            try
            {
                var target = new CallTraceCallHandler(LogWriter)
                {
                    LogBeforeCall     = true,
                    LogAsynchronously = true,
                    IncludeCallTime   = false,
                    IncludeParameters = false,
                    IncludePrincipal  = false,
                    IncludeReturnValue = true,
                };

                ITested tested = new Tested();
                var call = new MethodInvocationMock(
                                    tested,
                                    typeof(ITested).GetMethod("Method4"),
                                    new ParameterCollection(
                                            new object[] { "1", "2" },
                                            typeof(ITested).GetMethod("Method4").GetParameters(),
                                            p => true));

                var sw = new Stopwatch();

                sw.Start();
                var ret = target.Invoke(call, () => call.InvokeNext);
                sw.Stop();

                TestContext.WriteLine(
                    @"Total call duration: {0:d\.hh\:mm\:ss\.fffffff}",
                    sw.Elapsed);

                Thread.Sleep(200);

                //Assert.AreEqual("1_2", ret.ReturnValue);
                Assert.AreEqual(3, TestTraceListener.Messages.Count());
            }
            catch (UnitTestAssertException)
            {
                throw;
            }
            catch (Exception x)
            {
                TestContext.WriteLine("{0}", x.DumpString());
                throw;
            }
        }

        [TestMethod]
        public void Method1NoTraceTest()
        {
            try
            {
                var target = new CallTraceCallHandler(LogWriter);

                target.LogAsynchronously = false;
                target.LogBeforeCall     = false;
                target.LogAfterCall      = false;
                target.IncludeCallTime   = false;

                ITested tested = new Tested();
                var call = new MethodInvocationMock(
                                    tested,
                                    typeof(ITested).GetMethod("Method1"),
                                    new ParameterCollection(
                                            new object[] { "1" },
                                            typeof(ITested).GetMethod("Method1").GetParameters(),
                                            p => true));

                var sw = new Stopwatch();

                sw.Start();
                var ret = target.Invoke(call, () => call.InvokeNext);
                sw.Stop();

                TestContext.WriteLine(
                    @"Total call duration: {0:d\.hh\:mm\:ss\.fffffff}",
                    sw.Elapsed);

                //Thread.Sleep(_waitForLogFlush);

                Assert.AreEqual("1", ret.ReturnValue);
                Assert.AreEqual(1, TestTraceListener.Messages.Count());
            }
            catch (UnitTestAssertException)
            {
                throw;
            }
            catch (Exception x)
            {
                TestContext.WriteLine("{0}", x.DumpString());
                throw;
            }
        }

        [TestMethod]
        public void Method1FullTraceTest()
        {
            try
            {
                var target = new CallTraceCallHandler(LogWriter);

                target.LogAsynchronously = false;
                target.LogBeforeCall     = true;
                target.IncludeCallStack  = true;

                ITested tested = new Tested();
                var call = new MethodInvocationMock(
                                    tested,
                                    typeof(ITested).GetMethod("Method1"),
                                    new ParameterCollection(
                                            new object[] { "1" },
                                            typeof(ITested).GetMethod("Method1").GetParameters(),
                                            p => true));

                var sw = new Stopwatch();

                sw.Start();
                var ret = target.Invoke(call, () => call.InvokeNext);
                sw.Stop();

                TestContext.WriteLine(
                    @"Total call duration: {0:d\.hh\:mm\:ss\.fffffff}",
                    sw.Elapsed);

                //Thread.Sleep(_waitForLogFlush);

                Assert.AreEqual("1", ret.ReturnValue);
                Assert.AreEqual(3, TestTraceListener.Messages.Count());
            }
            catch (UnitTestAssertException)
            {
                throw;
            }
            catch (Exception x)
            {
                TestContext.WriteLine("{0}", x.DumpString());
                throw;
            }
        }

        [TestMethod]
        public void Method1DefaultTraceCallWithExceptionTest()
        {
            try
            {
                var target = new CallTraceCallHandler(LogWriter);

                target.LogAsynchronously = false;

                ITested tested = new Tested();
                var call = new MethodInvocationMock(
                                    tested,
                                    typeof(ITested).GetMethod("Method2"),
                                    new ParameterCollection(
                                            new object[] { "1" },
                                            typeof(ITested).GetMethod("Method2").GetParameters(),
                                            p => true));

                var sw = new Stopwatch();

                sw.Start();
                var ret = target.Invoke(call, () => call.InvokeNext);
                sw.Stop();

                TestContext.WriteLine(
                    @"Total call duration: {0:d\.hh\:mm\:ss\.fffffff}",
                    sw.Elapsed);

                //Thread.Sleep(_waitForLogFlush);

                Assert.IsNull(ret.ReturnValue);
                Assert.IsNotNull(ret.Exception);
                Assert.AreEqual(2, TestTraceListener.Messages.Count());
            }
            catch (UnitTestAssertException)
            {
                throw;
            }
            catch (Exception x)
            {
                TestContext.WriteLine("{0}", x.DumpString());
                throw;
            }
        }
    }
}
